#pragma once

#include "typedefs.h"
#include "bezier.h"

namespace outline {

// controls is poly bezier spline, containing one or more bezier pieces with common ends
template <class R>
class controls : 
	public std::vector< point<R> >,
	public typedefsR<R> 
{
public:
	typedef R real;
	typedef std::vector< point<R> > base_vector;
public:
	controls() { }
	controls(size_type count) : base_vector(count) { }
	template <class InputIterator>
	controls(InputIterator first, InputIterator last) : base_vector(first, last) { }

public:
	uint pieces() const
		{ return size() / 3; }
	uint nodes() const
		{ return closed() ? pieces() : pieces() + 1; }
	bezier_type piece(uint n) const
		{ return bezier_type((*this)[n*3], (*this)[n*3+1], (*this)[n*3+2], (*this)[n*3+3]); }
	bool closed() const
		{ return !empty() && front() == back(); }
	bool valid_size() const
		{ return size() > 1 && size() % 3 == 1; }
	void push_back(value_type const & val)
		{ base_vector::push_back(val); }
	void push_back(bezier_type const & piece) {
		if (empty())
			base_vector::push_back(piece.start());
		else
			assert(back() == piece.start());
		base_vector::push_back(piece.start_control());
		base_vector::push_back(piece.finish_control());
		base_vector::push_back(piece.finish());
	}
	//in & out tangents at given junction
	real_point_type junc_d1(uint j) const {
		assert(j <= pieces());
		if (j == 0) {
			assert (closed());
			return (*this)[size()-2] - (*this)[0];
		}
		return (*this)[j*3-1] - (*this)[j*3];
	}
	real_point_type junc_d2(uint j) const {
		assert(j <= pieces());
		if (j == pieces()) {
			assert (closed());
			return (*this)[1] - (*this)[0];
		}
		return (*this)[j*3+1] - (*this)[j*3];
	}
	/// sets for each smooth junction relation between left and right tangents, based on this spline controls
	void set_smooth_junc_lr(std::vector< junction<R> > & junc) const {
		uint jcount = junc.size();
		assert (valid_size());
		assert (jcount == pieces());
		for (uint j = 0; j < jcount; ++j) {
			if (junc[j].type != junc_smooth && junc[j].type != junc_smooth_affine)
				continue;

			real_point_type d1 = junc_d1(j);
			real_point_type d2 = junc_d2(j);

			R len_l = abs(d1);
			R len_r = abs(d2);
			R sign = scalar_product(d1, d2) > 0 ? 1 : -1;
			junc[j].r = len_r;
			junc[j].l = sign * len_l;
		}
	}
	void calc_tangents_relength(uint l, uint c, uint r, real & jl, real & jr, bool sign) const {
		point<R> dl = (*this)[l] - (*this)[c];
		point<R> dr = (*this)[r] - (*this)[c];
		real len_l = abs(dl);
		real len_r = abs(dr);
		jr = len_r;
		jl = len_l;
		if (jl == 0 && jr == 0) {
			jl = -1;
			jr = 1;
		}
		else {
			if (sign && scalar_product(dl, dr) < 0)
				jl = -jl;
			real f = 2 / (len_l + len_r);
			jl *= f;
			jr *= f;
		}
	}

	/// extracts part of spline between nodes b and e, both inclusive,
	/// for closed spline b is allowed to be greater than e, or equal, in that case a rotated copy of spline is returned
	void get_subctrls(uint b, uint e, controls & sub) const {
		assert (e <= nodes());
		if (e <= b && closed()) {
			sub.reserve(size() - (b-e)*3);
			sub.assign(begin() + b*3, end());
			sub.insert(sub.end(), begin() + 1, begin() + e*3 + 1);
			assert (sub.size() == size() - (b-e)*3);
			assert (e != b || sub.closed());
		}
		else if (e >= b)
			sub.assign(begin() + b*3, begin() + e*3 + 1);
		else 
			assert (false); ///all posibilities are exhausted
		assert (sub.valid_size());
	}
	/// replaces part of the spline between nodes b and e, both inclusive,
	/// with given spline; this spline preserves its length
	void set_subctrls(uint b, uint e, controls const & sub) {
		assert (e <= nodes());
		assert (sub.valid_size());
		if (e <= b && closed()) {
			assert (e != b || sub.closed());
			assert (sub.size() == size() - (b-e)*3);
			std::copy(sub.begin(), sub.begin() + (size() - b*3), begin() + b*3);
			std::copy(sub.begin() + (size() - b*3) - 1, sub.end(), begin());
		}
		else if (e >= b)
			std::copy(sub.begin(), sub.end(), begin() + b*3);
		else 
			assert (false); ///all posibilities are exhausted
	}
	template <class T>
	void transform(T const & t, controls & c) const {
		c.clear();
		c.reserve(size());
		for (uint i = 0; i < size(); ++i)
			c.push_back(t((*this)[i]));
	}
	/// rasterizes the poly bezier, so that a distance between two consecutive points
	/// does not exceed given threshold;
	/// sampling is carried out by calling given functor, that should have signature as follows:
	/// f(uint piece, R t, point<R> pos, point<R> tangent); // t is the time on given piece number
	/// if subdiv_min_twice is true, then subdivedes each Bezier piece at least twice, even if its length is less than max_distance
	template <class F>
	void sample(F const & f, R max_distance, bool include_last_point, bool subdiv_min_twice) const;
	void sample_points(std::vector< point<R> > & samples, R max_distance, bool include_last_point, bool subdiv_min_twice) const;
private:
	struct node;
	class append_pos;
};


} //namespace outline
